/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package estsapp.expertos;

import estsapp.entidades.DetallePedidoAProveedores;
import estsapp.entidades.EstadoPedidoAProveedores;
import estsapp.entidades.Material;
import estsapp.entidades.OrdenDeProduccion;
import estsapp.entidades.PedidoAProveedores;
import estsapp.entidades.Precio;
import estsapp.entidades.Proveedor;
import estsapp.estrategias.FabricaEstrategiasStock;
import estsapp.expertos.excepciones.ABMOrdenDeProducicionException;
import estsapp.expertos.excepciones.ABMPedidoAProveedoresException;
import estsapp.expertos.excepciones.ExpertoStockException;
import estsapp.utiles.convertidorDeUnidades.convertidorUnidades;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
import persistencia.Conexion;
import persistencia.Fachada;
import persistencia.exceptions.ExcepcionFachada;

/**
 *
 * @author Juan
 */
public class ExpertoABMPedidoAProveedores implements Experto {

    convertidorUnidades convertidor = new convertidorUnidades();

    //guardar varios pedidos en la BD, al prov de precio mas bajo
    public void armarPedidoProveedorPrecioMasBajo(List<DetallePedidoAProveedores> detallesPedido) {

        Criteria criterioProv = Fachada.getInstancia().crearCriterio(Proveedor.class);
        List prov = Fachada.getInstancia().buscar(Proveedor.class, criterioProv);

        for (Object proveedor : prov) {
            PedidoAProveedores pedido = new PedidoAProveedores();
            List<DetallePedidoAProveedores> detallesPedidoProv = new ArrayList<>();
            for (DetallePedidoAProveedores det : detallesPedido) {
                if (det.getMaterial().getMenorPrecio().getProveedor() == ((Proveedor) proveedor)) {
                    det.setCantidad(convertidor.cantidadUnidadesMaterial(det));
                    detallesPedidoProv.add(det);
                }
                pedido.setProveedor((Proveedor) proveedor);
                pedido.setDetallesPedido(detallesPedidoProv);

                setEstadoPedido(pedido, estado.PENDIENTE_CONFIRMACION);

                try {
                    Fachada.getInstancia().guardar(pedido);
                } catch (ExcepcionFachada ex) {
                    ex.printStackTrace();
                }
            }
        }

    }

    //guarda 1 solo pedido, al prov predeterminado
    public void armarPedidoProveedorPredeterminado(List<DetallePedidoAProveedores> detallesPedido, OrdenDeProduccion ordenProduccion) {

        Criteria criterioProv = Fachada.getInstancia().crearCriterio(Proveedor.class);
        criterioProv.add(Restrictions.eq("predeterminado", true));
        List prov = Fachada.getInstancia().buscar(Proveedor.class, criterioProv);


        PedidoAProveedores pedido = new PedidoAProveedores();
        pedido.setProveedor((Proveedor) prov.get(0));

        List<DetallePedidoAProveedores> detallesPedidoProv = new ArrayList<>();

        for (DetallePedidoAProveedores det : detallesPedido) {
            DetallePedidoAProveedores detalle = new DetallePedidoAProveedores();

            Float loteOptimoPieza = FabricaEstrategiasStock.getInstancia().getEstrategiaStock(det.getMaterial()).calcularLoteOptimo(det.getMaterial());
            if (loteOptimoPieza - det.getCantidad() > 0) {
                detalle.setCantidad(loteOptimoPieza);
            } else {
                detalle.setCantidad(det.getCantidad() + loteOptimoPieza);
            }

            detalle.setMaterial(det.getMaterial());
            pedido.getDetallesPedido().add(detalle);
        }

        setEstadoPedido(pedido, estado.PENDIENTE_CONFIRMACION);

        pedido.setFecha(new Date());
        pedido.setOrdenProduccion(ordenProduccion);


        try {
            Conexion.getInstancia().iniciarTX();
            Fachada.getInstancia().guardar(pedido);
            Conexion.getInstancia().confirmarTx();
        } catch (ExcepcionFachada ex) {
            Conexion.getInstancia().deshacerTx();
            ex.printStackTrace();
        }
    }

    public void armarPedidoProveedorPredeterminadoSinTX(List<DetallePedidoAProveedores> detallesPedido, OrdenDeProduccion ordenProduccion) throws ExcepcionFachada {

        Criteria criterioProv = Fachada.getInstancia().crearCriterio(Proveedor.class);
        criterioProv.add(Restrictions.eq("predeterminado", true));
        List prov = Fachada.getInstancia().buscar(Proveedor.class, criterioProv);


        PedidoAProveedores pedido = new PedidoAProveedores();
        pedido.setProveedor((Proveedor) prov.get(0));

        List<DetallePedidoAProveedores> detallesPedidoProv = new ArrayList<>();

        for (DetallePedidoAProveedores det : detallesPedido) {
            DetallePedidoAProveedores detalle = new DetallePedidoAProveedores();

            Float loteOptimoPieza = FabricaEstrategiasStock.getInstancia().getEstrategiaStock(det.getMaterial()).calcularLoteOptimo(det.getMaterial());
            if (loteOptimoPieza - det.getCantidad() > 0) {
                detalle.setCantidad(loteOptimoPieza);
            } else {
                detalle.setCantidad(det.getCantidad() + loteOptimoPieza);
            }

            detalle.setMaterial(det.getMaterial());
            pedido.getDetallesPedido().add(detalle);
        }

        setEstadoPedido(pedido, estado.PENDIENTE_CONFIRMACION);

        pedido.setFecha(new Date());
        pedido.setOrdenProduccion(ordenProduccion);


        Fachada.getInstancia().guardar(pedido);

    }

    public List buscarPedidosEnviados() {
        EstadoPedidoAProveedores estadoOrden = getEstadoOrden(estado.ENVIADO);
        Criteria enviado = Fachada.getInstancia().crearCriterio(PedidoAProveedores.class);
        enviado.add(Restrictions.eq("estado", estadoOrden));
        return Fachada.getInstancia().buscar(PedidoAProveedores.class, enviado);
    }

    public List buscarPedidosPendietnes() {
        EstadoPedidoAProveedores estadoPedido = getEstadoOrden(estado.PENDIENTE_CONFIRMACION);
        Criteria enviado = Fachada.getInstancia().crearCriterio(PedidoAProveedores.class);
        enviado.add(Restrictions.eq("estado", estadoPedido));
        return Fachada.getInstancia().buscar(PedidoAProveedores.class, enviado);
    }

    public void confirmarPedido(PedidoAProveedores pedido) {
        setEstadoPedido(pedido, estado.RECIBIDO);

        try {
            Conexion.getInstancia().iniciarTX();
            Fachada.getInstancia().guardar(pedido);
            Conexion.getInstancia().confirmarTx();
        } catch (ExcepcionFachada ex) {
            Conexion.getInstancia().deshacerTx();
            ex.printStackTrace();
        }
    }

    /**
     * Busca todos los proveedores, que tienen materiales contenidos en los
     * detalles del pedido
     *
     * @param pedido
     * @return
     */
    public List<Proveedor> buscarProveedoresCompatiblesPedido(PedidoAProveedores pedido) {
        List proveedores = new ArrayList();
        for (DetallePedidoAProveedores detallePedido : pedido.getDetallesPedido()) {
            for (Object proveedor : buscarProveedoresMaterial(detallePedido.getMaterial())) {
                agregarProveedorLista(proveedores, (Proveedor) proveedor);
            }
        }
        return proveedores;

    }

    /**
     * Devuelve todos los proveedores que proveen ese material
     *
     * @param material
     * @return
     */
    public List buscarProveedoresMaterial(Material material) {
        List proveedores = new ArrayList();
        for (Precio precio : material.getPrecios()) {
            proveedores.add(precio.getProveedor());
        }
        return proveedores;
    }

    private void agregarProveedorLista(List proveedores, Proveedor nuevoProv) {
        for (Object prov : proveedores) {
            if (prov.equals(nuevoProv)) {
                return;
            }
        }
        proveedores.add(nuevoProv);
    }

    /**
     * genera una nueva orden para aquellos materiales que se puedan cambiar de
     * proveedor y mantiene la orden anterior para aquellos que no se puede
     * cambiar el proveedor
     *
     * @param orden
     * @param nuevoProveedor
     */
    public void cambiarProveedorOrden(PedidoAProveedores orden, Proveedor nuevoProveedor) throws ABMPedidoAProveedoresException, ExcepcionFachada {

        if (orden.getProveedor().equals(nuevoProveedor)) {
            throw new ABMPedidoAProveedoresException("No se ha seleccionado un proveedor distinto.");
        }

        PedidoAProveedores nuevaOrden = new PedidoAProveedores();

        setEstadoPedido(nuevaOrden, estado.ENVIADO);

        nuevaOrden.setOrdenProduccion(orden.getOrdenProduccion());

        List<DetallePedidoAProveedores> nuevaListaDetalle = new ArrayList<>();
        boolean guardarOrdenVieja = false;

        //Se genera una nueva orden para los materiales que se puedan cambiar 
        //y mantiene los que no se puden cambiar de proveedor
        for (int i = 0; i < orden.getDetallesPedido().size(); i++) {
            DetallePedidoAProveedores detalleOrden = orden.getDetallesPedido().get(i);
            if (esProveedorMaterial(detalleOrden.getMaterial(), nuevoProveedor)) {
                DetallePedidoAProveedores nuevoDetalle = new DetallePedidoAProveedores();
                nuevoDetalle.setCantidad(detalleOrden.getCantidad());
                nuevoDetalle.setMaterial(detalleOrden.getMaterial());
                nuevaListaDetalle.add(nuevoDetalle);
                orden.getDetallesPedido().remove(i);
            } else {
                guardarOrdenVieja = true;
            }
        }
        nuevaOrden.setDetallesPedido(nuevaListaDetalle);
        nuevaOrden.setProveedor(nuevoProveedor);
        nuevaOrden.setFecha(Fachada.getInstancia().getFecha());
        try {
            Conexion.getInstancia().iniciarTX();
            if (guardarOrdenVieja) {
                Fachada.getInstancia().guardar(orden);
            }
            Fachada.getInstancia().guardar(nuevaOrden);
            Conexion.getInstancia().confirmarTx();
        } catch (ExcepcionFachada ex) {
            Conexion.getInstancia().deshacerTx();
            throw ex;
        }
        if (guardarOrdenVieja) {
            String materiales = "";
            for (DetallePedidoAProveedores detalle : orden.getDetallesPedido()) {
                materiales += "\n" + detalle.getMaterial().getNombre();
            }
            throw new ABMPedidoAProveedoresException("Los siguientes Materiales no se han podido cambiar de proveedor, ya que no se corresponden con el "
                    + "Proveedor seleccionado. \n"
                    + "" + materiales);
        }

    }

    /**
     * Retorna una lista de pedidos(que se encuentran Enviados) que pertenecen a
     * la orden de produccion,
     *
     * @param ordenProduccion
     * @return
     */
    public List buscarPedidosOrdenProduccionEspera(OrdenDeProduccion ordenProduccion) {
        Criteria criterioOrden = Fachada.getInstancia().crearCriterio(PedidoAProveedores.class);
        criterioOrden.add(Restrictions.eq("ordenProduccion", ordenProduccion));
        List pedidosEncontrados = Fachada.getInstancia().buscar(PedidoAProveedores.class, criterioOrden);

        Criteria estadoEspera = Fachada.getInstancia().crearCriterio(EstadoPedidoAProveedores.class);
        estadoEspera.add(Restrictions.eq("nombre", "Enviado"));
        EstadoPedidoAProveedores estado = (EstadoPedidoAProveedores) Fachada.getInstancia().buscar(EstadoPedidoAProveedores.class, estadoEspera).get(0);


        List pedidos = new ArrayList();
        for (Object obj : pedidosEncontrados) {
            PedidoAProveedores pedido = (PedidoAProveedores) obj;
            if (pedido.getEstado().equals(estado)) {
                pedidos.add(pedido);
            }
        }

        return pedidos;

    }

    /**
     * Coloca el pedido ingresado en estado Recibido, ¡¡¡¡NO USA TRANSACCIÓN!!!!
     *
     * @param pedido
     */
//    public void setEstadoPedidoRecibidoSinTX(PedidoAProveedores pedido) {
//        setEstadoPedido(pedido, estado.RECIBIDO);
//        try {
//            Fachada.getInstancia().guardar(pedido);
//        } catch (ExcepcionFachada ex) {
//            Logger.getLogger(ExpertoABMPedidoAProveedores.class.getName()).log(Level.SEVERE, null, ex);
//        }
//    }
    /**
     * regresa true si el provedor, provee ese material
     *
     * @param mat
     * @param prov
     * @return
     */
    private boolean esProveedorMaterial(Material mat, Proveedor prov) {
        for (Precio precio : mat.getPrecios()) {
            if (precio.getProveedor().equals(prov)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Setea el costo y el tiempo estimado de un pedido a proveedor
     *
     * @param listaPedidos
     */
    public void calcularCostoYTiempoProveedor(List listaPedidos) {
        for (Object object : listaPedidos) {
            PedidoAProveedores pedido = (PedidoAProveedores) object;
            if (pedido.getCostoOrden() == null) {
                pedido.setCostoOrden(calcularCosto(pedido));
            }
        }
    }

    /**
     * Regresa el costo un pedido realizado a un proveedor
     *
     * @param pedido
     * @return
     */
    private Float calcularCosto(PedidoAProveedores pedido) {
        Float costo = 0f;
        for (DetallePedidoAProveedores detalle : pedido.getDetallesPedido()) {
            costo += detalle.getCantidad() * detalle.getMaterial().getMenorPrecio().getMonto();
        }
        return costo;
    }

    /**
     * Coloca la orden de Pedido en estado Enviado.
     *
     * @param orden
     * @throws ExcepcionFachada
     */
    public void EnviarOrdenPedido(PedidoAProveedores orden) throws ExcepcionFachada {

        setEstadoPedido(orden, estado.ENVIADO);

        try {
            Conexion.getInstancia().iniciarTX();
            Fachada.getInstancia().guardar(orden);
            Conexion.getInstancia().confirmarTx();
        } catch (ExcepcionFachada ex) {
            Conexion.getInstancia().deshacerTx();
            throw ex;
        }
    }

    /**
     * Ingresa un pedido realizado a un proveedor, es cuando se reciben los
     * materiales
     *
     * @param pedido
     */
    public void ingresarPedido(PedidoAProveedores pedido) throws ABMPedidoAProveedoresException, ExcepcionFachada {

        ExpertoStock stockMaterial = (ExpertoStock) FabricaExpertos.getInstancia().getExperto(FabricaExpertos.expertos.STOCK);
        /**
         * 1) generar movimiento de stock para cada material 2) buscar la orden
         * de produccion vinculada y ponerla en produccion 3) si la orden tiene
         * otra orden de pedido que aun no ha llegado no se puede poner en
         * produccion 4) setear estado ingresada a la orden de pedido 5) no hay
         * 5
         */
        //########################################################################################################################
        //INICIA TX
        //########################################################################################################################
        Conexion.getInstancia().iniciarTX();

        //Por cada detalle de pedido genero una aumento de stok para el material
        for (DetallePedidoAProveedores detalle : pedido.getDetallesPedido()) {
            String observacion = "Ingreso de Stock Según orden de Producción: " + pedido.getOrdenProduccion().getNumero();
            Float cantidad = detalle.getCantidad();
            Material material = detalle.getMaterial();
            try {
                stockMaterial.aumentarStockPiezaSinTX(material, cantidad, observacion, Fachada.getInstancia().getFecha());
            } catch (ExpertoStockException ex) {
                Conexion.getInstancia().deshacerTx();
                throw new ABMPedidoAProveedoresException("Error al ingresar Pedido: \n" + ex.getMessage());
            } catch (ExcepcionFachada ex) {
                Conexion.getInstancia().deshacerTx();
                throw ex;
            }
        }
        boolean empezarProduccion = true;
        List peidosOrden = buscarPedidosOrdenProduccionEspera(pedido.getOrdenProduccion());
        for (Object object : peidosOrden) {
            PedidoAProveedores pedidoEncontrado = (PedidoAProveedores) object;
            //se encuentra un pedido que no ha llegado, por lo tanto no se puede empezar a producir
            if (!pedidoEncontrado.equals(pedido)) {
                empezarProduccion = false;
            }
        }
        if (empezarProduccion) {
            try {
                iniciarProduccion(pedido.getOrdenProduccion());
            } catch (ABMOrdenDeProducicionException ex) {
                Conexion.getInstancia().deshacerTx();
                throw new ABMPedidoAProveedoresException(ex.getMessage());
            }
        }
        setEstadoPedido(pedido, estado.RECIBIDO);

        Fachada.getInstancia().guardar(pedido);

        //########################################################################################################################
        //CONFIRMA TX
        //########################################################################################################################
        Conexion.getInstancia().confirmarTx();
    }

    private void iniciarProduccion(OrdenDeProduccion orden) throws ABMOrdenDeProducicionException {
        ExpertoABMOrdenDeProduccion ordenesProduccion = (ExpertoABMOrdenDeProduccion) FabricaExpertos.getInstancia().getExperto(FabricaExpertos.expertos.ABM_ORDEN_DE_PRODUCCION);
        ordenesProduccion.iniciarProduccionOrdenSinTX(orden);
    }

    private enum estado {

        PENDIENTE_CONFIRMACION, ENVIADO, RECIBIDO
    }

    private EstadoPedidoAProveedores getEstadoOrden(estado opcion) {
        String nombreEstado = "";
        switch (opcion) {
            case PENDIENTE_CONFIRMACION:
                nombreEstado = "Pendiente de Confirmación";
                break;
            case ENVIADO:
                nombreEstado = "Enviado";
                break;
            case RECIBIDO:
                nombreEstado = "Recibido";
        }
        Criteria criterioEstado = Fachada.getInstancia().crearCriterio(EstadoPedidoAProveedores.class);
        criterioEstado.add(Restrictions.eq("nombre", nombreEstado).ignoreCase());
        EstadoPedidoAProveedores estadopedido = (EstadoPedidoAProveedores) Fachada.getInstancia().buscar(EstadoPedidoAProveedores.class, criterioEstado).get(0);
        return estadopedido;
    }

    private void setEstadoPedido(PedidoAProveedores pedido, estado opcion) {
        pedido.setEstado(getEstadoOrden(opcion));
    }
}
